package com.ggshily.swf.abc;

import java.io.DataInput;

/**
 * The <code>abcFile</code> structure describes an executable code block with all its constant data, type descriptors, code, and metadata.
 * @author playfish_chg
 *
 */
public class ABCFile
{

	public short minor_version;
	public short major_version;
	
	/**
	 * a variable length structure composed of integers, doubles, strings, namespaces, 
	 * namespace sets, and multinames. These constants are referenced from other parts of the <code>abcFile</code> structure.
	 */
	public ABCConstantPool pool;
	public ABCMethod[] methods;
	
	/**
	 * 
	 */
	public ABCMetadata[] metadatas;
	
	/**
	 * U30, the number of entries in the <code>instance</code> and <code>class</code> arrays.
	 */
	public int classCount;
	
	/**
	 * <code>instance_info instance[class_count]</code>, this entry is a variable length 
	 * <code>instance_info</code> structure which specifies the characteristics of object instances created by a particular class.
	 */
	public ABCClassInstance[] instances;
	
	/**
	 * <code>class_info class[class_count]</code>, this entry defines the characteristics 
	 * of a class. It is used in conjunction with the <code>instance</code> field to derive a full description of an AS Class.
	 */
	public ABCClassInfo[] classes;
	
	/**
	 * Each script entry is a <code>script_info</code> structure that defines the characteristics of a single script in this file.
	 */
	public ABCScript[] scripts;
	
	/**
	 * <code>method_body_info method_body[method_body_count]</code>, this entry consists of a variable length 
	 * <code>method_body_info</code> structure which contains the instructions for an individual method or function.
	 */
	public ABCMethodBody[] methodBodys;

	public ABCFile()
	{

	}

	public ABCFile(DataInput di) throws Exception
	{
		
		minor_version = di.readShort();
		major_version = di.readShort();

		pool = new ABCConstantPool(di);

		int length = ABCUtil.readU30(di);
		methods = new ABCMethod[length];
		ABCUtil.readArray(methods, ABCMethod.class, di, length, this);

		length = ABCUtil.readU30(di);
		metadatas = new ABCMetadata[length];
		ABCUtil.readArray(metadatas, ABCMetadata.class, di, length);

		classCount = ABCUtil.readU30(di);
		instances = new ABCClassInstance[classCount];
		ABCUtil.readArray(instances, ABCClassInstance.class, di, classCount, this);

		classes = new ABCClassInfo[classCount];
		ABCUtil.readArray(classes, ABCClassInfo.class, di, classCount, this);

		length = ABCUtil.readU30(di);
		scripts = new ABCScript[length];
		ABCUtil.readArray(scripts, ABCScript.class, di, length);

		length = ABCUtil.readU30(di);
		methodBodys = new ABCMethodBody[length];
		ABCUtil.readArray(methodBodys, ABCMethodBody.class, di, length, this);
		
		if(classCount > 0)
		{
			System.out.println(getClassFile(0));
		}
	}
	
	public String[] getClassesFile()
	{
		String[] classes = new String[classCount];
		
		StringBuilder sb = new StringBuilder();
		for(int i = 0; i < classCount; i++)
		{
			sb.append(getClassFile(i));
		}
		
		return classes;
	}

	private String getClassFile(int index)
	{
		ABCClassInstance instance = instances[index];
		ABCClassInfo classInfo = classes[index];
		
		StringBuilder sb = new StringBuilder();
		
		sb.append("package \n");
		sb.append("{\n");
		
		sb.append("\tpublic class " + getMultinameString(instance.name));
		if(instance.superName > 0)
		{
			sb.append(" extends ").append(getMultinameString(instance.superName));
		}
		sb.append("\n");
		sb.append("\t{\n");
		sb.append(getClassInitializeMethodString(instance.iinit));
		sb.append(getTraitsString(instance.traits, false));
		sb.append(getTraitsString(classInfo.traits, true));
		sb.append("\t}\n");
		
		sb.append("}\n");
		
		return sb.toString();
	}

	private Object getClassInitializeMethodString(int cinit)
	{
		StringBuilder sb = new StringBuilder();
		sb.append("\t\tpublic function ").append(ABCUtil.getQName(pool.strings[methods[cinit].name]));
		sb.append(methods[cinit].getString(true));
		return sb.toString();
	}

	private String getMultinameString(int index)
	{
		return pool.multinames[index].nameStr;
	}

	public String getTraitsString(ABCTraitInfo[] traits, boolean isInClass)
	{
		StringBuilder sb = new StringBuilder();
		for(int i = 0; i < traits.length; i++)
		{
			sb.append(getTraitString(traits[i], isInClass));
		}
		return sb.toString();
	}

	public String getTraitString(ABCTraitInfo trait, boolean isInClass)
	{
		StringBuilder sb = new StringBuilder();
		if(trait.traitMethod != null)
		{
			ABCMultiname multiname = pool.multinames[trait.name];
			sb.append("\t\t");

			String ns = ABCNamespace.getName(pool.namespaces[multiname.ns].kind);
			if(ns.length() > 0)
			{
				sb.append(ns).append(" ");
			}
			
			if(isInClass)
			{
				sb.append("static ");
			}
			
			sb.append("function ").append(multiname.nameStr);
			sb.append(methods[trait.traitMethod.method].getString(false));
			
			return sb.toString();
		}
		return sb.toString();
	}
}
